package simulador;


import java.util.logging.Level;
import java.util.logging.Logger;
//import mips.estructurasMemoria.*;
import java.util.concurrent.Semaphore;
import java.util.Vector;

/**@author Fabián Guevara
 * @date 13-6-9
 * Representa un núcleo de la arquitectura Mips simplificada.
 * Dicha arquitectura consta de dos objetos  <code>Nucleo</code> que correrán
 * simultáneamente como hilos (de ahí que implemente el método <code>run()</code>)
 * y han de mantener coherencia de cachés, de modo que la caché de datos de cada
 * uno debe conocer a la caché de datos del otro. Para ello, antes de llamar al
 * método run (idealmente, justo después de crear al objeto <code>Nucleo</code>)
 * debe llamarse al método <code>asociarCacheDatosDelOtroNucleo(Nucleo)</code>
 * enviando como argumento una referencia al objeto <code>Nucleo</code> con el
 * cual debe mantenerse la coherencia de cachés.
 */

public class Nucleo implements Runnable{
    /**Puntero a la direccion de la siguiente instruccion a ejecutar*/
    private int pc;
    /**Registro de instrucción. Su valor corresponde a la instrucción apuntada
     por <code>pc</code>*/
    private Palabra ir;
    /**Arreglo que representa los 31 registros del núcleo. registro[0] ha de
     ser igual a 0 siempre*/
    private Palabra[] registro;
    /**Caché de datos del núcleo*/
    private CacheDatos cacheDatos;
    /**Caché de instrucciones del núcleo*/
    private Cache cacheInstrucciones;
    /**Memoria del núcleo*/
    private Memoria memoria;
    /**Número de registros en el núcleo*/
    public static final int NUM_REGISTROS = 32;
    /**Semáforo para sincronizar los ticks de reloj*/
    private final Semaphore mutexTicks;
    /**Objeto para sincronizar el acceso a la caché de datos mediante
     * synchronized statements
     */
    private final Object mutexCache;
    /* Tiempo de CPU de la instruccion*/
    private int reloj;
    /*Tiempo de los accesos a memoria */
    private final int tiempoAccesoMemoria;
    /* Instrucciones que se estan ejecutando en el nucleo */
    private Vector instruccionesEnEjecucion;
    /* Instrucciones que se que ya han terminado su ejecucion */
    private Vector instruccionesTerminadas;
    /*Indica que el programa ya llego a la instrucion halt*/
    private boolean halt;
    /**quantum del protocolo round-robin*/
    private final int quantum;

    private Contexto contextos[];


    private int idNucleo;


    /**Constructor. El nucleo se crea con 32 registros, inicializa en 0 al
     * registro 0 y crea las cachés de datos e instrucciones. Asigna  una
     * memoria, un pc inicial, un semáformo para sincronizarse con su dueño,
     * un semáforo para sincronizar con otros núcleos los accesos a caché de
     * datos, un quantum para el protocolo round-robin y un tiempo de acceso
     * a memoria.
     * @para m referencia a la memoria principal que usará el objeto Nucleo
     * @param pc pc inicial. Esto es, dirección de la primera instrucción
     * que ejecutará el núcleo
     * @param mt objeto (semáforo) para sincronizarse con el objeto creador
     * del núcleo. Idealmente, el objeto creador debera declarar como sección
     * crítica (usando esta variable como semáforo) al código donde controla
     * los ticks del reloj.
     * @param mc objeto (semáforo) para sincronizar con otros núcleos, los
     * accesos a la caché de datos.
     * @param q quantum del protocolo round-robin para la asignación del CPU.
     * Mediante este valor, el núcleo sabe cuándo es el momento (ciclo) a partir
     * del cual no debe permitir que ninguna otra instrucción ingrese al
     * pipeline
     * @param tam número de ciclos que tarda el núcleo en accesar (leer o
     * escribir) la memoria
     */
     public Nucleo(Memoria m, int pc, Semaphore mt, Object mc, int q, int tam){
        this.registro = new Palabra[NUM_REGISTROS];
        for(int i = 1; i < NUM_REGISTROS; i++){
            this.registro[i] = new Palabra();
        }
        this.registro[0] = new Palabra(0,0,0,0);    //Esto nunca debe variar
        this.cacheInstrucciones = new CacheInstrucciones(m);
        this.cacheDatos = new CacheDatos(m);
        this.pc = pc;
        this.mutexTicks = mt;
        this.mutexCache = mc;
        this.quantum = q;
        this.tiempoAccesoMemoria = tam;
        this.instruccionesEnEjecucion = new Vector();
        this.instruccionesTerminadas = new Vector();
        this.halt = false;
        this.memoria = m;
    }

     public int getReloj(){
        return reloj;
     }

    /**Le da a la caché de datos del objeto invocador, una referencia a la
     * caché de datos de otro objeto <code>Nucleo</code>, con el fin de lograr la
     * coherencia de cachés entre ambos.
     * @param otro referencia al objeto <code>Nucleo</code>
     * con el cual se debe mantener la coherencia de cachés.
     */
    public void asociarCacheDatosDelOtroNucleo(Nucleo otro){
        this.cacheDatos.asociarCacheDatosDelOtroNucleo(otro);
    }

    /**
     * Actualiza el reloj
     * @param ciclo de reloj actual
     */
    public void setReloj(int reloj) {
        this.reloj = reloj;
    }
    /**Devuelve el contenido de la caché en forma de arreglo bidimensional
     * de enteros
     * @return un arreglo bidimensional (matriz) con el contenido de la caché
     */
    public int[][] getContenidoCacheDatos(){
        return this.cacheDatos.getContenido();
    }


    public void ejecutar(Contexto hilo, int ciclosAEjecutar){
        
        cargarContexto(hilo);
        do{
            if(halt == false){
                this.ir = this.cacheInstrucciones.getPalabra(pc);
                this.instruccionesEnEjecucion.add(ir);
                if(this.instruccionesEnEjecucion.size()>1){
                    Palabra anterior = (Palabra) this.instruccionesEnEjecucion.elementAt(this.instruccionesEnEjecucion.size()-2);
                    this.ir.setInstruccionAnterior(anterior);
                    this.ir.setInstruccionTransanterior(new Palabra());
                }
                else if(this.instruccionesEnEjecucion.size()>2){
                    Palabra anterior = (Palabra) this.instruccionesEnEjecucion.elementAt(this.instruccionesEnEjecucion.size()-2);
                    Palabra trasAnterior = (Palabra) this.instruccionesEnEjecucion.elementAt(this.instruccionesEnEjecucion.size()-3);
                    this.ir.setInstruccionAnterior(anterior);
                    this.ir.setInstruccionTransanterior(trasAnterior);
                    }
                    else{
                        this.ir.setInstruccionAnterior(new Palabra());
                        this.ir.setInstruccionTransanterior(new Palabra());
                    }
                
                this.ejecutarInstruccion(this.ir);   
            }
            this.pc += 4;
            ciclosAEjecutar--;
            this.reloj++;
            terminoInstruccion();
        }while(ciclosAEjecutar > 0);


         while(this.instruccionesEnEjecucion.isEmpty()==false){
            this.reloj ++;
            this.terminoInstruccion();
         }
         this.cambiarContexto(hilo);
         
    }

    public void setContextos(Contexto[] contextos, int idNucleo) {
        this.contextos = contextos;
        this.idNucleo = idNucleo;
    }

    private void cambiarContexto(Contexto hilo){
        hilo.setHalt(this.halt);
        hilo.setPC(this.pc);
        hilo.setRegistros(this.registro);
    }

    private void cargarContexto(Contexto hilo){
        this.halt = hilo.getHalt();
        this.pc = hilo.getPC();
        Palabra[] reg = hilo.getRegistros();
        for(int i = 0; i < Nucleo.NUM_REGISTROS; i++)
            this.registro[i] = reg[i];
        registro[0] = new Palabra(0);
    }

/**
 * Busca en las instrucciones en el procesador las que ya han finalizado su ciclo de ejecución
 */
    private void terminoInstruccion(){
        for(int i = 0; i<this.instruccionesEnEjecucion.size();++i){
            Palabra elemento = (Palabra)this.instruccionesEnEjecucion.elementAt(i);
            if(this.reloj == elemento.getTiempoCPU()+elemento.getCicloInicial()){
                this.instruccionesTerminadas.add(elemento);
                this.instruccionesEnEjecucion.remove(i);
            }
        }
    }

    /**
     * Ejecucion de las instrucciones válidas en el procesador
     * @ instruccion actual a ejecutar
     */
    private void ejecutarInstruccion(Palabra instruccion){

        switch (instruccion.getByte(0) ){

            case 0 :{ //Load Link
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int desplazamiento = registro[instruccion.getByte(3)].toInt();
                int direccion = registroFuente1 + (desplazamiento * Palabra.NUM_BYTES_PALABRA);
                Palabra destino ;
                destino = this.cacheDatos.getPalabra(direccion);
                registro[instruccion.getByte(2)] = destino;
                this.registro[30] = new Palabra(direccion);
                // Calcular retrasos
                if(instruccion.getInstruccionAnterior().getByte(0) == 43){
                    asignarRetrasosLW(instruccion);
                }
                else{
                    asignarRetrasosIC(instruccion);
                }
                break;
            }
            case 1 :{ //Store Conditional
                  do{
                    int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                    int desplazamiento = registro[instruccion.getByte(3)].toInt();
                    int direccion = registroFuente1 + (desplazamiento * Palabra.NUM_BYTES_PALABRA);
                    if(this.registro[30].toInt() == direccion){
                        this.cacheDatos.escrituraPalabra(direccion, registro[instruccion.getByte(2)]);
                        this.registro[instruccion.getByte(2)].setByte(0,1);
                    }
                    else{
                        this.registro[instruccion.getByte(2)].setByte(0, 0);
                    }
                  }while(this.registro[instruccion.getByte(2)].getByte(0) == 0);
                break;
            }
            case 2 :{//Jump Register
                Palabra registroDestino = registro[instruccion.getByte(1)]; // Realiza la suma de los valores de los dos registros
                int newPC = registroDestino.getByte(0); // El PC de la siguiente instruccion es el valor del registroDestino
                newPC -= 4;
                this.pc = newPC;

                // Calcular retrasos
                asignarRetrasosIC(instruccion);
                break;
            }
            case 3 :{//Jump and Link
                //Palabra registroDestino = registro[instruccion.getByte(1)];
                registro[31].setByte(0, this.pc); //Guarda en R31 <- PC
                this.pc += instruccion.getByte(1)-4;

                // Calcular retrasos
                asignarRetrasosIC(instruccion);
                break;
            }
            case 4 :{ //BEQZ
                int saltoEtiqueta = instruccion.getByte(3);
                int registroDestino = registro[instruccion.getByte(1)].toInt(); // Realiza la suma de los valores de los dos registros

                if(registroDestino == 0){ // Branch es Tomado
                    this.pc = saltoEtiqueta - 4;

                }
                // Calcular retrasos
                asignarRetrasosIC(instruccion);
                break;
            }
            case 5 :{//BNEZ
                int saltoEtiqueta = instruccion.getByte(3);
                int registroDestino = registro[instruccion.getByte(1)].toInt(); // Realiza la suma de los valores de los dos registros

                if(registroDestino != 0){ // Branch es Tomado
                    this.pc = saltoEtiqueta - 4;

                }
                // Calcular retrasos
                asignarRetrasosIC(instruccion);
                break;
            }
            case 8:{ // DADDI
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int registroInmediato = registro[instruccion.getByte(3)].toInt();
                Palabra registroFuente = new Palabra(registroFuente1);  // Realiza la suma del valor del registro con un inmediato
                Palabra resultado = Palabra.suma(registroFuente,registroInmediato);
                registro[instruccion.getByte(2)] = resultado; //guarda en el registro especificado el valor de registroDestino

                // Calcular retrasos
                asignarRetrasosIR(instruccion);
                 break;
            }
            case 12 :{ //DMULT
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int registroFuente2 = registro[instruccion.getByte(2)].toInt();
                int resultado = registroFuente1 * registroFuente2; // Realiza la multiplicacion de los valores de los dos registros
                registro[instruccion.getByte(3)] = new Palabra(resultado); //guarda en el registro especificado el valor de registroDestino

                // Calcular retrasos
                asignarRetrasosRR(instruccion);
                break;
            }
            case 14 :{ //DDIV
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int registroFuente2 = registro[instruccion.getByte(2)].toInt();
                int resultado = registroFuente1 / registroFuente2; // Realiza la division de los valores de los dos registros
                registro[instruccion.getByte(3)] = new Palabra(resultado); //guarda en el registro especificado el valor de registroDestino

                // Calcular retrasos
                asignarRetrasosRR(instruccion);
                break;
            }
            case 32:{// DADD
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int registroFuente2 = registro[instruccion.getByte(2)].toInt();
                int resultado = registroFuente1 + registroFuente2; // Realiza la suma de los valores de los dos registros
                registro[instruccion.getByte(3)] = new Palabra(resultado); //guarda en el registro especificado el valor de registroDestino

                // Calcular retrasos
                asignarRetrasosRR(instruccion);
                break;
            }
            case 34 :{//DSUB
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int registroFuente2 = registro[instruccion.getByte(2)].toInt();
                int resultado = registroFuente1 - registroFuente2; // Realiza la resta de los valores de los dos registros
                registro[instruccion.getByte(3)] = new Palabra(resultado); //guarda en el registro especificado el valor de registroDestino

                // Calcular retrasos
                asignarRetrasosRR(instruccion);
                break;
            }
            case 35: { //LW
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int desplazamiento = registro[instruccion.getByte(3)].toInt();
                int direccion = registroFuente1 + (desplazamiento * Palabra.NUM_BYTES_PALABRA);

                Palabra destino ;
                destino = this.cacheDatos.getPalabra(direccion);
                registro[instruccion.getByte(2)] = destino;

                // Calcular retrasos
                if(instruccion.getInstruccionAnterior().getByte(0) == 43){
                    asignarRetrasosLW(instruccion);
                }
                else{
                    asignarRetrasosIC(instruccion);
                }
               break;
            }
            case 43: { //SW
                int registroFuente1 = registro[instruccion.getByte(1)].toInt();
                int desplazamiento = instruccion.getByte(3);
                int direccion = registroFuente1 + desplazamiento;
                int bloque = (direccion / 32) + 40;
                Palabra dato = new Palabra(registro[instruccion.getByte(2)].toInt());
                //dato = registro[instruccion.getByte(3)];

                if (cacheDatos.buscarEtiqueta(bloque)) { // Si no hay fallo de cache

                    //Se apodera de la cache de datos
                    //Escribe el dato en la cache de Datos
                    synchronized (mutexCache) {  //Se adueña de la Cache de datos y luego la libera
                         cacheDatos.escrituraPalabra(direccion, dato);//dato es un int deberia ser tipo Palabra
                    }
                    //Libera la cache de datos

                    //Se apodera de la otra cache de datos
                    synchronized (mutexCache) {  //Se adueña de la Cache de datos y luego la libera
                        this.cacheDatos.marcarComoInvalido(bloque);
                    }
                    //libera la otra cache de datos

                    //Se apodera de la memoria
                    synchronized (mutexCache) { //Se adueña de la memoria y luego la libera
                    //Escribir bloque en memoria
                    this.memoria.escribirPalabra(dato, direccion); //tiene que escribir el dato o sea la palabra en memoria
                    }
                    //libera la memoria
                }
                else { // Si hay fallo de cache

                    //Se apodera de la otra cache de datos
                    synchronized (mutexCache) {  //Se adueña de la Cache de datos y luego la libera
                        this.cacheDatos.marcarComoInvalido(bloque);
                    }
                    //Se libera la otra cache de datos

                     //Se apodera de la memoria
                    synchronized (mutexCache) {
                    //Escribir la palabra en memoria
                    this.memoria.escribirPalabra(dato, direccion); //tiene que escribir el dato o sea la palabra en memoria
                    }
                    //libera la memoria
                }
                // Calcular retrasos
                asignarRetrasosIC(instruccion);
                break;
            }
            case 63 :{// HALT
                instruccion.setTiempoCPU(5);
                this.halt = true;
                break;
            }
        }
    }
    /**
     * Esta clase representa una cache de 4 bloques, cada uno de 8 palabras
     * Una clase que derive de <code>Cache</code> deberá implementar el método
     * getPalabra(int).
     * @date 13-6-9*/
    private abstract class Cache{
        public static final int NUM_BLOQUES = 4;
        /**Matriz que representa el contenido del objeto <code>Cache</code>.
         * Las columnas corresponden a bloques y las filas a palabras dentro
         * de los bloques*/
        protected Palabra[][] cache;
        /**arreglo con las etiquetas (posición en memoria principal) de cada
         * bloque, de modo tal que etiqueta[i] corresponde al identificador
         * (único) del bloque asociado a la columna i de <code>cache</code>*/
        protected int etiqueta[];
        /**Referencia a la memoria principal*/
        protected Memoria memoria;
        /**Constructor. El objeto creado tiene una matriz de palabras de 4x8
         (4 bloques, 8 palabras cada uno). Inicialmente, no hay ningún bloque
         cargado. El valor centinela para indicar tal condición es -1.
         Ningua palabra dentro del objeto está inicializada.*/
        public Cache(Memoria m){
            this.memoria = m;
            this.cache = new Palabra[NUM_BLOQUES][8];
            this.etiqueta = new int[NUM_BLOQUES];
            for(int i =0; i<NUM_BLOQUES; ++i)
                etiqueta[i] = -1;
            for(int fil = 0; fil < NUM_BLOQUES; fil++){
                for(int col = 0; col < 8; col++){
                    this.cache[fil][col] = new Palabra();
                }
            }
        }
        /**Devuelve el contenido de la caché en forma de arreglo bidimensional
         * de enteros
         * @return un arreglo bidimensional (matriz) con el contenido de la caché
         */
        public int[][] getContenido(){
            int[][] resultado = new int[8][NUM_BLOQUES];
            for(int fil = 0; fil < NUM_BLOQUES; fil++){
                for(int col = 0; col < 8; col++){
                    resultado[col][fil] = this.cache[fil][col].toInt();
                    //nótese la inversión de columnas y filas. Esto porque la
                    //matriz que se muestra al usuario, es la transpuesta de
                    //la matriz de la caché.
                }
            }
            return resultado;
        }
        /**Obtiene una determinada palabra. Si la palabra no está en caché,
         * el bloque completo al que pertenece es traído de la memoria principal
         * y se coloca en la caché siguiendo la estrategia de mapeo directo.
         * @param direccion absoluta de la palabra deseada
         */
        public abstract Palabra getPalabra(int direccion);
    }// fin private class CacheInstruciones
    /**@date 14-6-9
     * Representa una caché de datos. Tiene todas las propiedades de la clase
     * <code>Cache</code> pero agrega lo necesario para mantener coherencia de
     * cachés con la otra caché de datos de la arquitectura mips; esto es, un bit
     * de validez para cada bloque y una referencia a la otra caché de datos para
     * solicitarle invalidación de bloque cuando sea el caso.
     */
    private class CacheDatos extends Cache{
        private boolean valido[];
        /**Referencia a la caché de datos con la cual se debe mantener coherencia
         * de cachés.
         */
        CacheDatos colega;
        public CacheDatos(Memoria m){
            super(m);
            valido = new boolean[NUM_BLOQUES];
            for(int i =0; i<NUM_BLOQUES; ++i){
                valido[i] = false;
            }

        }
        /**
         * Le da al objeto invocador, una referencia a la caché de datos
         * con la cual debe mantener coherencia de cachés.
         * @param otro referencia al objeto <code>Nucleo</code> con cuya caché
         * de datos debe mantener coherencia de cahés el objeto invocador
         */
        public void asociarCacheDatosDelOtroNucleo(Nucleo otro){
            this.colega = otro.cacheDatos;
        }
        /**Obtiene una determinada palabra. Si la palabra no está en caché,
         * el bloque completo al que pertenece es traído de la memoria principal
         * y se coloca en la caché siguiendo la estrategio de mapeo directo y si
         * la caché de datos del otro núcleo del sistema tiene el mismo bloque
         * (esto es, un bloque con igual etiqueta), dicho bloque se marca como
         * no válido en la caché de datos del otro núcleo.
         * @param direccion absoluta de la palabra deseada
         * @throws ArrayIndexOutOfBoundsException si <code>dir</code>
         * es mayor que MAX_MEMORIA_DATOS
         * @throws ArrayIndexOutOfBoundsException si <code>dir</code>
         * es menor que 0.
         */
        public Palabra getPalabra(int dir)throws ArrayIndexOutOfBoundsException{
            if(dir > Memoria.MAX_MEMORIA_DATOS){
                throw new ArrayIndexOutOfBoundsException("La dirección sobrepasa "+
                        "la memoria de datos");
            }
            Palabra resultado = null;
            int numeroBloque;
            int etiquetaBloque = numeroBloque = dir / (NUM_BLOQUES * Memoria.NUM_PALABRAS_BLOQUE);
            if(numeroBloque > NUM_BLOQUES)
                numeroBloque %= NUM_BLOQUES;
            int posicionBloque = (dir - (etiquetaBloque * Palabra.NUM_BYTES_PALABRA * Memoria.NUM_PALABRAS_BLOQUE)) / Palabra.NUM_BYTES_PALABRA;
    
            if(estaEncache(dir)){
                this.colega.marcarComoInvalido(etiquetaBloque);
                return resultado = cache[numeroBloque][posicionBloque];
            }
            else{
                Palabra[] palabrasBloque = this.memoria.leerBloqueDatos(etiquetaBloque);
                for(int j = 0; j < Memoria.NUM_PALABRAS_BLOQUE;++j){
                    cache[numeroBloque][j] = palabrasBloque[j];
                    
                }
                this.etiqueta[numeroBloque] = etiquetaBloque;
                this.valido[numeroBloque] = true;
                resultado = cache[numeroBloque][posicionBloque];
            }
            return resultado;
        }
        /**Marca un bloque en la caché como inválido
         * @param etiqueta etiqueta del bloque que se desea invalidar
         * @return true si el bloque estaba en la caché (y por lo tanto,
         * se invalidó), false si el bloque no estaba.
         */
        private synchronized boolean marcarComoInvalido(int etiqueta){
            boolean estaba = false;
            for(int i = 0; i < 4; i++){
                if(this.etiqueta[i] == etiqueta){
                    estaba = true;
                    this.valido[i] = false;
                }
            }
            return estaba;
        }

        /*
         * determina si se encuentra el bloque de memoria en cache
         * @param ubicacion en memoria de donde se encuentra la palabra
         * @return Verdadero si el bloque esta en cache
         */
        private boolean estaEncache(int direccion){
            boolean estaba = false;
            int etiquetaBloque = direccion / (Memoria.NUM_BLOQUES * Memoria.NUM_PALABRAS_BLOQUE);

            for(int i = 0;i<4;++i){
                if(this.valido[i] == true )
                    if(this.etiqueta[i] == etiquetaBloque){
                        estaba = true;
                    }
            }
            return estaba;
        }

        public boolean buscarEtiqueta(int bloque){
            boolean estaba = false;
            for(int i = 0;i<4;++i){
                if(this.etiqueta[i] == bloque ){
                    estaba = true;
                }
            }
            return estaba;
        }

        //Escribe un dato en el campo de la palabra que le corresponda en la cahe de Datos
        void escrituraPalabra(int dir, Palabra dato) {
            int numeroBloque;
            int etiquetaBloque = numeroBloque = dir / (NUM_BLOQUES * Memoria.NUM_PALABRAS_BLOQUE);
            if(numeroBloque > NUM_BLOQUES)
                numeroBloque %= NUM_BLOQUES;
            int posicionBloque = (dir - (etiquetaBloque * Palabra.NUM_BYTES_PALABRA * Memoria.NUM_PALABRAS_BLOQUE)) / Palabra.NUM_BYTES_PALABRA;
            cache[numeroBloque][posicionBloque] = dato;
        }
    }
    /**Representa una chaché de instrucciones. Define un rango de la memoria
     * para instrucciones, a saber [MIN_MEMORIA_INSTRUCCIONES, MAX_MEMORIA_INSTRUCCIONES]
     */
    private class CacheInstrucciones extends Cache{
        public CacheInstrucciones(Memoria m){
            super(m);
        }
        /**Obtiene una determinada palabra. Si la palabra no está en caché,
         * el bloque completo al que pertenece es traído de la memoria principal
         * y se coloca en la caché siguiendo la estrategio de mapeo directo.
         * @param direccion absoluta de la palabra deseada
         * @throws ArrayIndexOutOfBoundsException si la dirección es mayor que
         * MAX_MEMORIA_INSTRUCCIONES
         * @throws ArrayIndexOutOfBoundsException si la dirección es menor que 0.
         */
        public Palabra getPalabra(int dir)throws ArrayIndexOutOfBoundsException{
            if(dir > Memoria.MAX_MEMORIA_INSTRUCCIONES){
                throw new ArrayIndexOutOfBoundsException("La dirección sobrepasa "+
                        "la memoria de instrucciones");
            }
            Palabra resultado = null;
            
            int numeroBloque;
            int etiquetaBloque = numeroBloque = dir / (NUM_BLOQUES * Memoria.NUM_PALABRAS_BLOQUE);
            if(numeroBloque > NUM_BLOQUES)
                numeroBloque %= NUM_BLOQUES;
            int posicionBloque = (dir - (etiquetaBloque * Palabra.NUM_BYTES_PALABRA * Memoria.NUM_PALABRAS_BLOQUE)) / Palabra.NUM_BYTES_PALABRA;
            
            if (estaEncache(dir) == true) {
                return resultado = cache[numeroBloque][posicionBloque];
            }
            else {
                Palabra[] palabrasBloque = this.memoria.leerBloqueInstrucciones(etiquetaBloque);
                for (int j = 0; j < Memoria.NUM_PALABRAS_BLOQUE; ++j) {
                    cache[numeroBloque][j] = palabrasBloque[j];

                }
                this.etiqueta[numeroBloque] = etiquetaBloque;
                resultado = cache[numeroBloque][posicionBloque];
            }
            return resultado;
        }

                /*
         * determina si se encuentra el bloque de memoria en cache
         * @param ubicacion en memoria de donde se encuentra la palabra
         * @return Verdadero si el bloque esta en cache
         */
        private boolean estaEncache(int direccion){
            boolean estaba = false;
            int etiquetaBloque = direccion / (Memoria.NUM_BLOQUES * Memoria.NUM_PALABRAS_BLOQUE);

            for(int i = 0; i < NUM_BLOQUES; ++i){
                if(this.etiqueta[i] == etiquetaBloque){
                    estaba = true;
                }
            }
            return estaba;
        }






    }//Fin de la clase CacheInstrucciones

    public void run(){
        System.out.println("Corriendo el nucleo");
        boolean continuar = true;
        int indiceContexto = 0;

        do{
            try {
                mutexTicks.acquire(1);
                this.ejecutar(this.contextos[indiceContexto], reloj);
                ++indiceContexto;
                if(indiceContexto > (this.contextos.length - 1))
                    indiceContexto = 0;
                //imprimir estado
                mutexTicks.release(1);
            } catch (InterruptedException ex) {
                Logger.getLogger(Nucleo.class.getName()).log(Level.SEVERE, null, ex);
            }
        }while(continuar == true);

    }

    /**
     * Calcula el retraso para las instrucciones tipo Inmediato-Registro
     * @param instruccion que esta en CPU
     * @param instruccion que entra a CPU un ciclo antes que la actual
     * @param instruccion que entra a CPU 2 ciclos antes que la actual
     */

    public void asignarRetrasosIR(Palabra instrActual){
        int retraso=0;
        int direccionMemoria=0;

        if(instrActual.getByte(1) == instrActual.getInstruccionAnterior().getByte(2) || instrActual.getByte(1) == instrActual.getInstruccionAnterior().getByte(3) || instrActual.getByte(1) == instrActual.getInstruccionAnterior().getByte(1)){ //Retraso por conflicto de datosSi es un daddi o si son op reg
            retraso += 2;
        }
        else if(instrActual.getByte(1) == instrActual.getInstruccionTransanterior().getByte(2) || instrActual.getByte(1) == instrActual.getInstruccionTransanterior().getByte(3)){
            retraso +=1;
        }
        if(instrActual.getByte(0) == 35 || instrActual.getByte(0) == 43){
            direccionMemoria = instrActual.getByte(1) + instrActual.getByte(3);
            if(cacheDatos.estaEncache(direccionMemoria)){//Esta el bloque en cache
                retraso += 0;
            }
            else{ // fallo cache
                retraso += this.tiempoAccesoMemoria;
            }
        }
        int retrasoAnterior = instrActual.getInstruccionAnterior().getTiempoCPU() - 5;
        int retrasoTransAnterior = instrActual.getInstruccionTransanterior().getTiempoCPU()-5;
        if(retrasoAnterior > 0)
            retraso += retrasoAnterior; //-5 = tiempo minimo de ejecucion, Calcula el retraso de esta instruccion
        if(retrasoTransAnterior  > 0)
            retraso += retrasoTransAnterior;
        instrActual.asignarTiempos(this.reloj,retraso);
    }
    /**
     * Calcula el retraso para las instrucciones tipo Registro-Registro
    */
    public void asignarRetrasosRR(Palabra instrActual){
        int retraso = 0;
        if((instrActual.getByte(2) == instrActual.getInstruccionAnterior().getByte(2) || instrActual.getByte(2) == instrActual.getInstruccionAnterior().getByte(3)) || (instrActual.getByte(3) == instrActual.getInstruccionAnterior().getByte(2) || instrActual.getByte(3) == instrActual.getInstruccionAnterior().getByte(3) )){
            retraso += 2;
        }
        if((instrActual.getByte(2) == instrActual.getInstruccionTransanterior().getByte(2) || instrActual.getByte(2) == instrActual.getInstruccionTransanterior().getByte(3)) || (instrActual.getByte(3) == instrActual.getInstruccionTransanterior().getByte(2) || instrActual.getByte(3) == instrActual.getInstruccionTransanterior().getByte(3) )){
            retraso += 1;
        }

        int retrasoAnterior = instrActual.getInstruccionAnterior().getTiempoCPU() - 5;
        int retrasoTransAnterior = instrActual.getInstruccionTransanterior().getTiempoCPU()-5;
        if(retrasoAnterior > 0)
            retraso += retrasoAnterior; //-5 = tiempo minimo de ejecucion, Calcula el retraso de esta instruccion
        if(retrasoTransAnterior  > 0)
            retraso += retrasoTransAnterior;
        instrActual.asignarTiempos(this.reloj,retraso);
    }
    /**
     * Calcula el retraso para las instrucciones de Control
    */
    public void asignarRetrasosIC(Palabra instrActual){
        int retraso = 0;
        int direccionMemoria = 0;
        if(instrActual.getByte(0) == 43){
            if((instrActual.getByte(2) == instrActual.getInstruccionAnterior().getByte(2)) || (instrActual.getByte(2) == instrActual.getInstruccionAnterior().getByte(3))){
                retraso += 2;
            }
            else if((instrActual.getByte(2) == instrActual.getInstruccionTransanterior().getByte(2)) || (instrActual.getByte(2) == instrActual.getInstruccionTransanterior().getByte(3))){
                retraso += 1;
            }
            direccionMemoria = instrActual.getByte(1) + instrActual.getByte(3);
            if(cacheDatos.estaEncache(direccionMemoria)){//Esta el bloque en cache
                retraso += 0;
            }
            else{ // fallo cache
                retraso += this.tiempoAccesoMemoria;
            }
        }
        else{
            if((instrActual.getByte(1) == instrActual.getInstruccionAnterior().getByte(2)) || (instrActual.getByte(1) == instrActual.getInstruccionAnterior().getByte(3))){
                retraso += 2;
            }
            else if((instrActual.getByte(1) == instrActual.getInstruccionTransanterior().getByte(2)) || (instrActual.getByte(1) == instrActual.getInstruccionTransanterior().getByte(3))){
                retraso += 1;
            }
        }
        int retrasoAnterior = instrActual.getInstruccionAnterior().getTiempoCPU() - 5;
        int retrasoTransAnterior = instrActual.getInstruccionTransanterior().getTiempoCPU()-5;
        if(retrasoAnterior > 0)
            retraso += retrasoAnterior; //-5 = tiempo minimo de ejecucion, Calcula el retraso de esta instruccion
        if(retrasoTransAnterior  > 0)
            retraso += retrasoTransAnterior;
        instrActual.asignarTiempos(this.reloj,retraso);
    }

    public void asignarRetrasosLW(Palabra instrActual){
        int retraso=0;
        int direccionMemoria=0;
        retraso += 2;
        direccionMemoria = instrActual.getByte(1) + instrActual.getByte(3);
        if(cacheDatos.estaEncache(direccionMemoria)){//Esta el bloque en cache
            retraso += 0;
        }
        else{ // fallo cache
            retraso += this.tiempoAccesoMemoria * 8;
        }
        int retrasoAnterior = instrActual.getInstruccionAnterior().getTiempoCPU() - 5;
        int retrasoTransAnterior = instrActual.getInstruccionTransanterior().getTiempoCPU()-5;
        if(retrasoAnterior > 0)
            retraso += retrasoAnterior; //-5 = tiempo minimo de ejecucion, Calcula el retraso de esta instruccion
        if(retrasoTransAnterior  > 0)
            retraso += retrasoTransAnterior;
        instrActual.asignarTiempos(this.reloj,retraso);
    }
}